/* * Copyright 2011 Konrad Malawski <konrad.malawski@project13.pl> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package pl.project13.janbanery.core.rest; import com.google.gson.Gson; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.ListenableFuture; import com.ning.http.client.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pl.project13.janbanery.config.Configuration; import pl.project13.janbanery.core.rest.response.NingRestClientResponse; import pl.project13.janbanery.core.rest.response.RestClientResponse; import pl.project13.janbanery.encoders.FormUrlEncodedBodyGenerator; import pl.project13.janbanery.exceptions.RestClientException; import pl.project13.janbanery.exceptions.ServerCommunicationException; import pl.project13.janbanery.resources.KanbaneryResource; import java.io.IOException; import java.lang.reflect.Type; import java.util.concurrent.ExecutionException; /** * A simple Facade to {@link AsyncHttpClient}. * <p/> * Used to encapsulate all the logic going on when we call a RESTful webservice via the * AsyncHttpClient. It's great but there's too many lines involved in doing a GET and parsing * it back into our OOP World. * * @author Konrad Malawski */ public class AsyncHttpClientRestClient extends RestClient { private Logger log = LoggerFactory.getLogger(getClass()); private Configuration conf; private Gson gson; private AsyncHttpClient asyncHttpClient; private FormUrlEncodedBodyGenerator encodedBodyGenerator; public AsyncHttpClientRestClient(AsyncHttpClient asyncHttpClient) { this.asyncHttpClient = asyncHttpClient; } public AsyncHttpClientRestClient(Configuration conf, Gson gson, AsyncHttpClient asyncHttpClient, FormUrlEncodedBodyGenerator encodedBodyGenerator) { this.conf = conf; this.gson = gson; this.asyncHttpClient = asyncHttpClient; this.encodedBodyGenerator = encodedBodyGenerator; } @Override public void init(Configuration configuration, Gson gson, FormUrlEncodedBodyGenerator encodedBodyGenerator) { this.conf = configuration; this.gson = gson; this.encodedBodyGenerator = encodedBodyGenerator; } @Override public RestClientResponse doPost(String url, KanbaneryResource resource) { AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient.preparePost(url); authorize(requestBuilder); String requestBody = encodedBodyGenerator.asString(resource); log.info("Generated request body is: '{}'", requestBody); setFormUrlEncodedBody(requestBuilder, requestBody); RestClientResponse response = execute(requestBuilder); verifyResponseCode(response); if (log.isDebugEnabled()) { log.debug("Got response for creating resource: {}", response.getResponseBody()); } return response; } @Override @SuppressWarnings("unchecked") public <T> T doPost(String url, KanbaneryResource resource, Class<?> returnType) { RestClientResponse response = doPost(url, resource); String responseBody = response.getResponseBody(); return (T) gson.fromJson(responseBody, returnType); } /** * Perform a plain GET call onto the passed URL. * It will be authorized using {@link Configuration#authorize(com.ning.http.client.AsyncHttpClient.BoundRequestBuilder)} * method. * * @param url url to call * @return the RestClientResponse object containing the server response to our call */ @Override public RestClientResponse doGet(String url) { log.info("Calling GET on: " + url); AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient.prepareGet(url); authorize(requestBuilder); RestClientResponse response = execute(requestBuilder); verifyResponseCode(response); return response; } /** * Delegate to {@link AsyncHttpClientRestClient#doGet(String)} and also use {@link Gson} to parse the returned json * into the requested object * * @param url url to call * @param returnType taskType to parse the returned json into * @param <T> the return taskType, should match returnType * @return the KanbaneryResource created by parsing the retrieved json * @throws ServerCommunicationException if the response body could not be fetched */ @Override @SuppressWarnings("unchecked") public <T> T doGet(String url, Type returnType) throws ServerCommunicationException { RestClientResponse response = doGet(url); String responseBody = response.getResponseBody(); return (T) gson.fromJson(responseBody, returnType); } @Override public RestClientResponse doDelete(String url) { log.info("Calling DELETE on: " + url); AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient.prepareDelete(url); authorize(requestBuilder); RestClientResponse response = execute(requestBuilder); verifyResponseCode(response); return response; } @Override public RestClientResponse doPut(String url, String requestBody) { log.info("Calling PUT on: '" + url + "', with data: " + requestBody); AsyncHttpClient.BoundRequestBuilder requestBuilder = asyncHttpClient.preparePut(url); authorize(requestBuilder); setFormUrlEncodedBody(requestBuilder, requestBody); RestClientResponse response = execute(requestBuilder); verifyResponseCode(response); return response; } @Override @SuppressWarnings({"unchecked"}) public <T> T doPut(String url, String requestBody, Class<?> returnType) { RestClientResponse response = doPut(url, requestBody); String responseBody = response.getResponseBody(); return (T) gson.fromJson(responseBody, returnType); } @Override @SuppressWarnings({"unchecked"}) public <T> T doPut(String url, String requestBody, Type returnType) throws ServerCommunicationException { RestClientResponse response = doPut(url, requestBody); String responseBody = response.getResponseBody(); return (T) gson.fromJson(responseBody, returnType); } @Override @SuppressWarnings("unchecked") public <T> T doPut(String url, KanbaneryResource requestObject, Class<?> returnType) { String requestBody = encodedBodyGenerator.asString(requestObject); RestClientResponse response = doPut(url, requestBody); String responseBody = response.getResponseBody(); return (T) gson.fromJson(responseBody, returnType); } public void authorize(AsyncHttpClient.BoundRequestBuilder requestBuilder) { conf.authorize(requestBuilder); } public void setFormUrlEncodedBody(AsyncHttpClient.BoundRequestBuilder requestBuilder, String requestBody) { requestBuilder.setBody(requestBody); requestBuilder.setHeader("Content-Type", "application/x-www-form-urlencoded"); } /** * Execute and throw RestClientException exceptions if the request could not be executed. * * @param requestBuilder the request to be executed() * @return return the response fetched from the server * @throws RestClientException if the response could not be fetched */ public RestClientResponse execute(AsyncHttpClient.BoundRequestBuilder requestBuilder) throws RestClientException { Response response; try { ListenableFuture<Response> futureResponse = requestBuilder.execute(); response = futureResponse.get(); if (log.isDebugEnabled()) { // the if is here so that we don't call the getResponseBody() when we're not going to print it log.debug("Got response body: {}", response.getResponseBody()); } } catch (InterruptedException e) { throw new RestClientException("Interrupted while waiting for server response", e); } catch (ExecutionException e) { throw new RestClientException("Tried to retrieve result from aborted action.", e); } catch (IOException e) { throw new RestClientException("Encountered IOException while executing REST request.", e); } return new NingRestClientResponse(response); } /** * If is very important that you call this method after you're finished working with kanbanery. * It will close all underlying threads and free a lot of memory used by the RestClient. */ @Override public void close() { asyncHttpClient.close(); } }